#include <QApplication>
#include <QQuickItem>
#include <QQuickWindow>
#include <QQmlContext>
#include <QMessageBox>
#include <QXmppRosterManager.h>
#include <QXmppVCardManager.h>
#include <QXmppVersionManager.h>
#include <QXmppMessage.h>
#include <QDir>
#include <QBuffer>
#include <QImageReader>
#include <QSslError>
#include <QXmppUtils.h>

#include "config/Config.h"
#include "persist/DbManager.h"
#include "multimedia/AudioPlayer.h"
#include "model/RosterModel.h"
#include "model/SessionsModel.h"
#include "model/LocalUserModel.h"
#include "model/GroupsModel.h"
#include "util/PhotoUtil.h"
#include "ApplicationEngine.h"

X_BEGIN_NAMESPACE

const QString ApplicationEnginePrivate::ROOT_VIEW = "qrc:/qml/view/RootView.qml";

//----------------------------------------
// ApplicationEngine 类
//----------------------------------------
ApplicationEngine::ApplicationEngine(QObject *parent)
    : QQmlApplicationEngine(parent)
    , d(new ApplicationEnginePrivate)
{
    connectXmppSignals();
}

ApplicationEngine::ApplicationEngine(const ApplicationEngine &src)
    : QQmlApplicationEngine(src.parent())
    , d(src.d)
{
    connectXmppSignals();
}

ApplicationEngine::ApplicationEngine(const QUrl &url, QObject *parent)
    : QQmlApplicationEngine(url, parent)
    , d(new ApplicationEnginePrivate)
{
    connectXmppSignals();
}

ApplicationEngine::ApplicationEngine(const QString &filePath, QObject *parent)
    : QQmlApplicationEngine(filePath, parent)
    , d(new ApplicationEnginePrivate)
{
    connectXmppSignals();
}

ApplicationEngine &ApplicationEngine::operator =(const ApplicationEngine &rhs)
{
    if(this != &rhs) d = rhs.d;

    return *this;
}

/**
 * @brief 执行。引擎执行时，会将自身设置到QML上下文中，作为一个特殊的全局变量engine
 */
void ApplicationEngine::run()
{
    rootContext()->setContextProperty("engine", this);

    load(QUrl(ApplicationEnginePrivate::ROOT_VIEW));
    connectRootViewSignals();
}

/**
 * @brief 连接来自QXMPP库的信号。
 */
void ApplicationEngine::connectXmppSignals(){
    QXmppVersionManager &vm = versionManager();
    vm.setClientName(APP_NAME);
    vm.setClientVersion(APP_VERSION);
    vm.setClientOs(QSysInfo::kernelType() + ":" + QSysInfo::kernelVersion() + ":" + QSysInfo::buildAbi());

    xmppClient().logger()->setLoggingType(QXmppLogger::FileLogging);

    // Client signals
    QXmppClient* client = &xmppClient();
    QObject::connect(client, SIGNAL(connected()), this, SLOT(onXmppServerConnected()));
    QObject::connect(client, SIGNAL(disconnected()), this, SLOT(onXmppServerDisconnected()));
    QObject::connect(client, SIGNAL(stateChanged(QXmppClient::State)), this, SLOT(onXmppClientStateChanged(QXmppClient::State)));
    QObject::connect(client, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(onXmppMessageReceived(QXmppMessage)));
    QObject::connect(client, SIGNAL(presenceReceived(QXmppPresence)), this, SLOT(onXmppPresenceReceived(QXmppPresence)));
    QObject::connect(client, SIGNAL(iqReceived(QXmppIq)), this, SLOT(onXmppIqReceived(QXmppIq)));
    QObject::connect(client, SIGNAL(error(QXmppClient::Error)), this, SLOT(onXmppClientConnecionError(QXmppClient::Error)));
    QObject::connect(client, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(onXmppSslErrors(QList<QSslError>)));

    // Roster signals
    QXmppRosterManager* rosterMgr = &rosterManager();
    QObject::connect(rosterMgr, SIGNAL(rosterReceived()), this, SLOT(onRosterReceived()));
    QObject::connect(rosterMgr, SIGNAL(presenceChanged(QString,QString)), this, SLOT(onPresenceChanged(QString,QString)));
    QObject::connect(rosterMgr, SIGNAL(subscriptionReceived(QString)), this, SLOT(onSubscriptionReceived(QString)));
    QObject::connect(rosterMgr, SIGNAL(itemAdded(QString)), this, SLOT(onItemAdded(QString)));
    QObject::connect(rosterMgr, SIGNAL(itemChanged(QString)), this, SLOT(onItemAdded(QString)));
    QObject::connect(rosterMgr, SIGNAL(itemRemoved(QString)), this, SLOT(onItemRemoved(QString)));

    // VCard signals
    QXmppVCardManager* vcardMgr = &vcardManager();
    QObject::connect(vcardMgr, SIGNAL(vCardReceived(QXmppVCardIq)), this, SLOT(onVCardReceived(QXmppVCardIq)));
    QObject::connect(vcardMgr, SIGNAL(clientVCardReceived()), this, SLOT(onClientVCardReceived()));
}

/**
 * @brief 连接来自RootView的信号。
 */
void ApplicationEngine::connectRootViewSignals()
{
    const QObject* root = rootObjects().first();

    QObject::connect(root, SIGNAL(qsSignIn(QString,QString,int)), this, SLOT(onSignIn(QString,QString,int)));
    QObject::connect(root, SIGNAL(qsSignOut()), this, SLOT(onSignOut()));
}

/**
 * @brief 获取XMPP客户端对象。
 * @return
 */
QXmppClient& ApplicationEngine::xmppClient(){
    return d->m_xmppClient;
}

/**
 * @brief 获取联系人名单管理器。
 * @return
 */
QXmppRosterManager& ApplicationEngine::rosterManager(){
    return d->m_xmppClient.rosterManager();
}

/**
 * @brief VCard管理器。
 * @return
 */
QXmppVCardManager& ApplicationEngine::vcardManager(){
    return d->m_xmppClient.vCardManager();
}

/**
 * @brief 版本管理。
 * @return
 */
QXmppVersionManager &ApplicationEngine::versionManager()
{
    return d->m_xmppClient.versionManager();
}

/**
 * @brief 登录。
 *
 * @param userName
 * @param password
 * @param availableStatus
 */
void ApplicationEngine::onSignIn(const QString& userName, const QString& password, int availableStatus){
    QString jid = userName + Config::instance().domain(true) + "/xchat";
    int port = Config::instance().port();

    QXmppPresence::AvailableStatusType availableStatusType = QXmppPresence::AvailableStatusType(availableStatus);

    xmppClient().configuration().setJid(jid);
    xmppClient().configuration().setPassword(password);
    xmppClient().configuration().setPort(port);
    xmppClient().clientPresence().setAvailableStatusType(availableStatusType);

    if(Config::instance().ignoreSslError()) {
        xmppClient().configuration().setIgnoreSslErrors(true);
    }

    xmppClient().connectToServer(xmppClient().configuration());
}

/**
 * @brief 退出当前登录用户.
 */
void ApplicationEngine::onSignOut(){
    xmppClient().disconnectFromServer();
}

/**
 * @brief 登录成功。
 *
 * 登录成功前，QXMPP会经历以下事件：
 * - TCP socket connection
 * - Client sends start stream
 * - Server sends start stream
 * - TLS negotiation (encryption)
 * - Authentication
 * - Resource binding
 * - Session establishment
 */
void ApplicationEngine::onXmppServerConnected(){
    LocalUser.setJid(xmppClient().configuration().jid());
    LocalUser.setBareJid(QXmppUtils::jidToBareJid(xmppClient().configuration().jid()));
    LocalUser.setPassword(xmppClient().configuration().password());

    // 准备本地数据
    if(!DbManager::instance().prepareData(LocalUser.bareJid())){
        qApp->exit();
        return;
    }

    // 加载
    SessionsModel::instance().loadDataFromDb();
    RosterModel::instance().loadDataFromDb();
    GroupsModel::instance().loadDataFromDb();

    AudioPlayer::instance().playSystemSound(AudioPlayer::LoginOk);

    emit signInSucceed();
}

/**
 * @brief 服务器断开事件。
 */
void ApplicationEngine::onXmppServerDisconnected(){
    // 清理
    SessionsModel::instance().houseKeep();
    RosterModel::instance().houseKeep();
    GroupsModel::instance().houseKeep();

    // 关闭数据库
    DbManager::instance().closeDataBase();

    AudioPlayer::instance().playSystemSound(AudioPlayer::Logout);

    emit signOutSucceed();
}

/**
 * @brief 客户端连接状态改变事件。如：正在连接，已连接，未连接。
 *
 * @param state
 */
void ApplicationEngine::onXmppClientStateChanged(QXmppClient::State state){
    switch (state) {
        case QXmppClient::ConnectingState:
            {
                qDebug() << "Server State: Connecting to the server...";
                break;
            }

        case QXmppClient::ConnectedState:
            {
                qDebug() << "Server State: Connected!";
                break;
            }

        case QXmppClient::DisconnectedState:
            {
                qDebug() << "Server State: Disconnected!";
                break;
            }

        default:
            break;
    }
}

/**
 * @brief 消息接收事件。
 *
 * @param message
 */
void ApplicationEngine::onXmppMessageReceived(const QXmppMessage &message){
    if(message.body().isEmpty()) return;

    qDebug() << "Got message from :" << message.from();
    switch(message.type()){
        case QXmppMessage::Error:
            qDebug() << "Got error message: " << message.body();
            break;
        case QXmppMessage::Normal:
            qDebug() << "Got normal message: " << message.body();
            break;
        case QXmppMessage::Chat:
            {
                //qDebug() << "Got chat message: " << message.body();
                QString bareJid = QXmppUtils::jidToBareJid(message.from());
                SessionModel* session = (SessionModel*)SessionsModel::instance().findSession(bareJid);
                if(session == NULL){
                    session = &SessionsModel::instance().createSession(bareJid, SessionType::P2P);
                }

                Q_ASSERT(session != NULL);
                session->receivedMessage(message.from(), message.body(), MessageContentType::Text, message.stamp());

                AudioPlayer::instance().playSystemSound(AudioPlayer::IncomingMessage);
            }
            break;
        case QXmppMessage::GroupChat:
            qDebug() << "Got group message: " << message.body();
            break;
        case QXmppMessage::Headline:
            qDebug() << "Got headline message: " << message.body();
            break;
        default:
            break;
    }
}

/**
 * @brief 出席状态事件。
 *
 * 初次登录时会接收到联系人的出席状态事件，用于刷新联系人的在线状态。
 *
 * @param presence
 */
void ApplicationEngine::onXmppPresenceReceived(const QXmppPresence &presence){
    QXmppPresence::AvailableStatusType availableStatusType = presence.availableStatusType(); // 用户状态：在线，忙碌，离开，聊天等
    QXmppPresence::Type type = presence.type(); // 出席类型：可用，不可用，可订阅，不可订阅等
    QString statusText = presence.statusText();
    QString from = presence.from();

    qDebug() << "User:" << from << ", available status: " << availableStatusType
             << ", presence type: " << type;
}

/**
 * @brief Info-Query，查询事件。
 *
 * 当客户端向服务端发出特定的查询请求后，服务响应时将接收到此消息。
 * 查询请求可分为：消息，名片，MUC发起人，联系人名单，会话，版本等。
 * 即这是一类似于搜索的协议，查询各种信息。
 *
 * QXmppIq是所有特定查询的基类。
 *
 * @param iq
 */
void ApplicationEngine::onXmppIqReceived(const QXmppIq& iq){
    qDebug() << "XmppIqReceived: ";
}

/**
 * @brief 客户端连接错误事件。
 *
 * @param error
 */
void ApplicationEngine::onXmppClientConnecionError(QXmppClient::Error error){
    switch(error){
        case QXmppClient::SocketError:
            {
                // There is still has an ssl error after SocketError, we must ignore this case
                if(!d->m_sslError){
                    emit signInFailed("Login Failed", "Network error, please retry later.s");

                    xmppClient().disconnectFromServer();
                }
            }
            break;

        case QXmppClient::KeepAliveError:
            {
                break;
            }

        case QXmppClient::XmppStreamError:
            {
                break;
            }

        case QXmppClient::NoError:
        default:
            {
                break;
            }
    }
}

/**
 * @brief 客户端连接SSL问题事件。
 *
 * 与客户端连接错误事件不同，此事件只用于自签名证书错误等。遇到SSL证书问题时，
 * 只需要设置为忽略SSL错误即可，不用断开再重新连接服务器。
 *
 * @param errors
 */
void ApplicationEngine::onXmppSslErrors(const QList<QSslError>& errors){
    foreach (QSslError sslError, errors) {
        QSslError::SslError se = sslError.error();
        //qDebug() << "SSL error: " << se;

        bool trust = false;
        if(Config::instance().ignoreSslError()){
            trust = true;
        } else{
            QString message;
            if(se == QSslError::CertificateSignatureFailed) message = "签名验证失败，是否继续?";
            if(se == QSslError::HostNameMismatch) message = "证书与主机名不匹配，是否继续?";
            if(se == QSslError::CertificateUntrusted) message = "此证书不可信任，是否继续?";

            int result = QMessageBox::warning(NULL, "SSL证书出错", message, QMessageBox::No | QMessageBox::Yes);
            if(result == QMessageBox::Yes){
                trust = true;
            }
        }

        d->m_sslError = true;
        xmppClient().configuration().setIgnoreSslErrors(trust);

        break;
    }
}

/**
 * @brief 名单事件。
 *
 * 此事件在客户端执行联系人名单IQ，服务端返回数据后触发。在此事件之前，名单内容是空的。
 * 联系人名单需要在此事件之后才能调用 getRosterBareJids() 与 getRosterEntry() 方法获取。
 */
void ApplicationEngine::onRosterReceived(){
    QStringList bareJids = rosterManager().getRosterBareJids();
    foreach (const QString& bareJid, bareJids) {
        QXmppRosterIq::Item item = rosterManager().getRosterEntry(bareJid);

        // 当前联系人仅支持在一个分组中
        QString groupName;
        QSet<QString>::const_iterator it = item.groups().constBegin();
        if(it != item.groups().constEnd()){
            groupName = *it;
        }

        RosterModel::instance().saveContact(bareJid, Config::instance().defaultAvatar(), PhotoType::Png, groupName);
    }

    // 请求当前登录用户的名片
    QString result = vcardManager().requestClientVCard();
    qDebug() << "Requesting client vcard " << result;
}

/**
 * @brief 出席状态或资源改变事件。
 *
 * @param bareJid
 * @param resource
 *
 * 特定用户或资源发生改变时会引发此事件。
 */
void ApplicationEngine::onPresenceChanged(const QString &bareJid, const QString &resource){

}

/**
 * @brief 当一个联系人请求订阅你的状态时会引发此事件。
 *
 * 你可以通过 acceptSubscription() 接受或 refuseSubscription() 拒绝。
 *
 * @param bareJid
 */
void ApplicationEngine::onSubscriptionReceived(const QString &bareJid){

}

/**
 * @brief 当名单中的特定ID增加时引发此事件。
 *
 * @param bareJid
 */
void ApplicationEngine::onItemAdded(const QString &bareJid){

}

/**
 * @brief 当名单中的特定ID改变时引发此事件。
 *
 * @param bareJid
 */
void ApplicationEngine::onItemChanged(const QString &bareJid){

}

/**
 * @brief 当名单中的特定ID删除时引发此事件。
 *
 * @param bareJid
 */
void ApplicationEngine::onItemRemoved(const QString &bareJid){

}

/**
 * @brief 电子名片从服务端返回事件。
 *
 * 当客户端调用 clientVCard() 向服务端查询好友的名片时，服务端会返回此事件。
 *
 * @param vcardIq
 */
void ApplicationEngine::onVCardReceived(const QXmppVCardIq &vcardIq){
    QString jid = vcardIq.to();
    qDebug() << "Contact's vcard received: " << jid;
}

/**
 * @brief 登录用户的名片接收事件。
 *
 * 当客户端调用 requestClientVCard() 向服务端查询自身的名片时，服务端会返回此事件。
 */
void ApplicationEngine::onClientVCardReceived(){
    QXmppVCardIq vCard = vcardManager().clientVCard();
    QString bareJid = QXmppUtils::jidToBareJid(vCard.to());

    QString VCardsPath = Config::instance().storagePath(bareJid, Config::VCards);
    QString userName = bareJid.split("@")[0];

    // 保存照片
    QString photoPath = PhotoUtil::instance().saveToLocalByBareJid(vCard.photo(), bareJid);

    // 设置当前登录用户的状态及名片信息，然后更新至数据库
    LocalUser.setJid(xmppClient().configuration().jid());
    LocalUser.setBareJid(bareJid);
    LocalUser.setPhoto(photoPath);
    LocalUser.setPhotoType(PhotoType::Png);
    LocalUser.setDescription(vCard.description());
    LocalUser.setFirstName(vCard.firstName());
    LocalUser.setLastName(vCard.lastName());
    LocalUser.setNickName(vCard.nickName());
    LocalUser.setEmail(vCard.email());
    LocalUser.setAddress(vCard.addresses().isEmpty() ? "-" : vCard.addresses().first().street());
    LocalUser.setUrl(vCard.url());
    LocalUser.setTelephone(vCard.phones().isEmpty() ? "-" : vCard.phones().first().number());    // 手机与电话暂时设置成一致
    LocalUser.setMobile(vCard.phones().isEmpty() ? "-" : vCard.phones().first().number());
    LocalUser.setBirthday(vCard.birthday());
    LocalUser.setAvailableStatus(AvailableStatus((int)xmppClient().clientPresence().availableStatusType()));
    LocalUser.updateVCard();

    // 保存名片XML文件，调试使用
    QFile file(VCardsPath + userName + ".xml");
    if(file.open(QIODevice::ReadWrite))
    {
        QXmlStreamWriter stream(&file);
        vCard.toXml(&stream);
        file.close();
    }
}

X_END_NAMESPACE
